package com.Algorithm;

import com.Algorithm.Interface.DecisionMakingInterface;
import com.Entity.Employee;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 *
 * @author Prezes
 */
public class Promethee implements DecisionMakingInterface {

    private int numberOfCriterions;
    
    private double[][] model;
    private List<Employee> employeesIn;
    private double[] weights;
    private double[] preferenceThresholds;
    
    public Promethee(double[] weights)
    {
        numberOfCriterions = Criterion.NUMBER_OF_CRITERIONS;
        
        this.weights = weights;
        
        preferenceThresholds = new double[numberOfCriterions];
        preferenceThresholds[0] = 25.0;
        preferenceThresholds[1] = 9.0;
        preferenceThresholds[2] = 0.5;
        preferenceThresholds[3] = 0.5;
        preferenceThresholds[4] = 0.7;
        preferenceThresholds[5] = 15.0;
    }
    
    /**
     * This method sets preference threshold of specific criterion for the preference function.
     * @param criterion - index of specific criterion, e.g. 0 for the first, 1 for the second etc.
     * @param value - value of preference threshold
     */
    public void setPreferenceThreshold(int criterion, double value)
    {
        preferenceThresholds[criterion] = value;
    }
    
    /**
     * This method returns difference between values of criterion function for two specific employees.
     * @param x1 - value of criterion function for employee 1
     * @param x2 - value of criterion function for employee 2
     */
    private double difference(double x1, double x2)
    {
        return x1 - x2;
    }
    
    /**
     * This method returns value of preference function for two specific employees and specific preference threshold.
     * @param x1 - value of criterion function for employee 1
     * @param x2 - value of criterion function for employee 2
     * @param p - value of preference threshold
     */
    private double preferenceFunction(double x1, double x2, double p)
    {
        if (difference(x1, x2) <= 0)
        {
            return 0;
        } 
        else if (difference(x1, x2) <= p)
        {
            return difference(x1, x2) / p;
        }
        else
        {
            return 1;
        }
    }
    
    /**
     * This method returns value of degree of preference for two specific employees. This value is calcualted as a weighted sum of preference function values.
     * @param employee1 - index of employee 1 in the model matrix
     * @param employee2 - index of employee 2 in the model matrix
     */
    private double degreeOfPreference(int employee1, int employee2)
    {
        double degree = 0.0;
        for (int i = 0; i < numberOfCriterions; i++)
        {
            degree += weights[i] * preferenceFunction(
                    model[employee1][i], 
                    model[employee2][i], 
                    preferenceThresholds[i]);
        }
        return degree;
    }
    
    /**
     * This method returns value of positive flow of preference for specific employee.
     * @param employee - index of employee in the model matrix
     */
    private double positiveFlowOfPreference(int employee)
    {
        double degreeSum = 0.0;
        for (int i = 0; i < model.length; i++)
        {
            degreeSum += degreeOfPreference(employee, i);
        }
        return (1.0 / model.length) * degreeSum;
    }
    
    /**
     * This method returns value of negative flow of preference for specific employee.
     * @param employee - index of employee in the model matrix
     */
    private double negativeFlowOfPreference(int employee)
    {
        double degreeSum = 0.0;
        for (int i = 0; i < model.length; i++)
        {
            degreeSum += degreeOfPreference(i, employee);
        }
        return (1.0 / model.length) * degreeSum;
    }
    
    /**
     * This method returns value of net flow for specific employee.
     * @param employee - index of employee in the model matrix
     */
    private double netFlow(int employee)
    {
        return positiveFlowOfPreference(employee) - negativeFlowOfPreference(employee);
    }
    
    /**
     * This method build a model for promethee algorithm. The model is a matrix with employees in rows and criterions in columns.
     * It also set default weights for each criterion.
     * @param employees - list of employees that will be ranked by promethee
     */
    @Override
    public void buildModel(List<Employee> employees)
    {
        employeesIn = employees;
        
        model = new double[employeesIn.size()][numberOfCriterions];
        
        for (int i = 0; i < model.length; i++)
        {
            Employee e = employeesIn.get(i);
            Criterion c = new Criterion(e);
            
            model[i][0] = c.criterion_1();
            model[i][1] = c.criterion_2();
            model[i][2] = c.criterion_3();
            model[i][3] = c.criterion_4();
            model[i][4] = c.criterion_5();
            model[i][5] = c.criterion_6();
        }
        
        System.out.println("\nPROMETHEE MODEL");
        for (int i = 0; i < model.length; i++)
        {
            for (int j = 0; j < model[i].length; j++)
            {
                System.out.printf("%7.2f", model[i][j]);
            }
            System.out.print("\n");
        }
    }
    
    /**
     * This method calculates net flow for each variant (employee) and then returns the ranking as a list of employees sorted by net flow value. 
     * @return list of variants
     */
    @Override
    public List<Variant> determineRanking() 
    {
        List<Variant> variants = new ArrayList<>();
        for (int i = 0; i < model.length; i++)
        {
            variants.add(new Variant(employeesIn.get(i), netFlow(i)));
        } 
        
        System.out.println("\nPROMETHEE PRZEPŁYWY");
        for (int i = 0; i < variants.size(); i++)
        {
            System.out.printf("%-30s %7.2f\n", 
                    variants.get(i).getEmployee().getName() + " " + variants.get(i).getEmployee().getSurname(), 
                    variants.get(i).getValue());
        } 
        
        Collections.sort(variants);
        return variants;
    }
    
}
